#include "lib.h"
#include "process.h"
#include "waitqueue.h"
#include "kmem.h"
#include "serial.h"
#include "mmu.h"
#include "mmu_asm.h"

/* Adresse an der das Datensegment virtuell eingblendet werden soll */
#define DATASEGMENT_VIRTUAL_ADDRESS (0xB0000000)
#define EXECSEGMENT_VIRTUAL_ADDRESS (0xA0000000)

/// User- und Kernelstackgröße für Start eines Prozesses
#define STACK_SIZE 4096

/**
 * Assembler dummyroutine, die beim Laden eines Prozesses eingetragen wird.
 * INIT_JUMP_PROCESS kehrt beim Aufruf direkt aus dem Systemmodus zurück 
 * und startet so Prozess.
 */
void INIT_JUMP_PROCESS();

process_t *__current_process;
static int process_id_seed;

/**
 * Alloziiert und initialisiert Speicher für Prozessverwaltunsblock
 */
static process_t *alloc_process();

/**
 * Gibt Prozessverwaltungsblock wieder frei
 */
static void free_process(process_t *process);

/**
 * Initialisiert Stack des Prozesses mit Argumenten
 */
static void *init_user_stack(void* user_stack, void *virt_user_stack,
                             char* args, int *nargs);

/**
 * Initialisiere Prozessverwaltungsblock mit Prozessroutine und lade
 * Prozessargumente auf Stack
 */
static bool init_process( process_t *process, void* process_start, char* args);

/**
 * Initialisiert Prozesszustand, so dass Prozess beim nächsten Prozesswechsel
 * automatisch startet
 */
static void setup_cpu_state(process_t *process, void *process_start, 
                            void *kernel_stack, void *user_stack, int nargs);

/**
 i Starte initialisierten Prozess
 */
static bool start_process( process_t *process );

/**
 * Wählt den nächsten Prozess für Kontextwechsel aus.
 */
static process_t *scheduler(process_t *current);

/**
 * Führt Kontextwechsel aus
 */
static void swap_process(process_t *current, process_t *next);

/**
 * Assemblerroutine, die jeweiligen Prozesszustand über Kernelstacks austauscht.
 */
void switch_cpu(cpu_state_t **from, cpu_state_t **to);





process_t *
current_process()
{
    return __current_process;
}
    
bool
load_process( void* process_start, char* args )
{   
  /*
    process_t *process = alloc_process();
    if(!process) {
      return false;
    }
    
    if(!init_process( process, process_start, args)) {
      free_process( process );
      return false;
    }

    if(!start_process( process )) {
      free_process( process );
      return false;
    }
  */
    return true;
}

static process_t *
alloc_process()
{
    process_t *process = kmalloc(sizeof(process_t));
    if(!process) return NULL;

    process->state = PROCESS_EXISTENT;
    process->process_id = process_id_seed++;
    list_node_init(&process->process_node);

    process->process_mem.exec_segment.start = mmu_alloc_pages(1);
    process->process_mem.exec_segment.size = (1<<20);
    process->process_mem.data_segment.start = mmu_alloc_pages(1);
    process->process_mem.data_segment.size = (1<<20);

    /* Verschieben den User-Stack an das Ende des 1MB Datensegments */
    process->process_mem.user_stack.start =
            (void*)((unsigned long)process->process_mem.data_segment.start + (1<<20) - STACK_SIZE);
    process->process_mem.user_stack.size = STACK_SIZE;

    /* Alloziere Kernelstack */
    process->process_mem.kernel_stack.start = kmalloc( STACK_SIZE );
    process->process_mem.kernel_stack.size = STACK_SIZE;
    //error 2
    if(!process->process_mem.kernel_stack.start) {
        kfree(process->process_mem.user_stack.start, STACK_SIZE);
        kfree(process, sizeof(process_t));
        return NULL;
    }
    return process;
}

static void
free_process(process_t *process)
{
    if(process) kfree( process, sizeof(process_t) );
}

static void *
init_user_stack(void* user_stack, void *virt_user_stack, char* args, int *nargs)
{       
    int i;
    char *stack_args;
    *nargs = 0;       

    void *start = user_stack;

    //copy arguments into user memory
    user_stack = (void*)( (unsigned long)user_stack - (strlen(args) + 1));
    strcpy(user_stack, args);

    stack_args = user_stack;        //save pointer to arguments 

    //Allignment des Stackpointers anpassen
    while( (unsigned long)user_stack & 3) {
        user_stack = (void*) ( PTR_ADDR(user_stack) &~ 3 );
    }   


    //Trenne Argumente bei Leerzeichen
    for(i = strlen(stack_args) - 1;i >= 0; i--) {
        if( stack_args[i] == ' ') stack_args[i] = 0; //do split argument

        //if new arg, put pointer onto stack and increase number of arguments
        if(i==0 || (stack_args[i] != 0 && stack_args[i-1] == ' ')) {
            (*nargs)++;
            user_stack = (void*) (PTR_ADDR(user_stack) - sizeof(char*));
            *(char**)user_stack = &stack_args[i];
        }
    }

    return (void*)(PTR_ADDR(virt_user_stack) + 
                   (PTR_ADDR(user_stack) - PTR_ADDR(start)));
}

static bool
init_process( process_t *process, void* process_start, char* args)
{
    void *mmu = process->process_mem.data_segment.start;

    //bestimme Adresse fuer Stackpointer
    void *kernel_stack = 
            ((unsigned char*)process->process_mem.kernel_stack.start) 
            + process->process_mem.kernel_stack.size - 8;

    process->process_mem.user_stack.start =
            ((unsigned char*)DATASEGMENT_VIRTUAL_ADDRESS) +
             process->process_mem.data_segment.size - 
             process->process_mem.user_stack.size;

    void *virt_user_stack = (void*)
            (PTR_ADDR(process->process_mem.user_stack.start) +
             process->process_mem.user_stack.size - 4);
    void *phy_user_stack = (void*)(PTR_ADDR(mmu) +
                                   process->process_mem.data_segment.size - 4);

    int nargs;
    virt_user_stack = init_user_stack( phy_user_stack, virt_user_stack,
                                       args, &nargs );

    setup_cpu_state(process, process_start, kernel_stack, virt_user_stack, nargs);
  return true;
}

static void
setup_cpu_state(process_t *process, void *process_start, 
                void *kernel_stack, void *user_stack, int nargs)
{

    *((unsigned long*)kernel_stack - 1) = (unsigned long)process_start;
    cpu_state_t *cpu_state = (cpu_state_t*)(PTR_ADDR(kernel_stack) 
                             - sizeof(cpu_state_t) - 4);

    memset(cpu_state, 0, sizeof(cpu_state_t));
    cpu_state->r14 = 0;
    cpu_state->r13 = (register_t)user_stack;
    cpu_state->pc = (register_t)INIT_JUMP_PROCESS;
    kprintf("Starte Prozess im Usermode und erlaube interrupts\n");
    cpu_state->spsr = 0x50; //Starte Prozess im Usermode und erlaube interrupts
    cpu_state->r0   = (register_t)nargs;
    cpu_state->r1   = (register_t)user_stack;

    process->cpu_state = cpu_state;
}

static bool 
start_process( process_t *process )
{   
    process->state = PROCESS_READY;
    wq_push( &active_list, process );
    return TRUE;
}  

void 
reschedule()
{
    process_t *process = current_process();
    process_t *next_process = scheduler(process);
    //kprintf("TEsta<°)))><: %x", next_process->cpu_state->pc);
    if(process != next_process){
        swap_process(process, next_process);
    }
}

static process_t *
scheduler(process_t *current)
{
    //Schiebe aktuellen Prozess in active_list
    if(current && current->state == PROCESS_RUNNING){
        wq_enqueue( &active_list, current);
    }

    //bestimme naechsten Prozess
    process_t *next = wq_pop( &active_list );

    if(next == NULL) while(1);
    return next;
}

static void 
swap_process(process_t *current, process_t *next)
{
    //save shared resources and set state
    if(current){
        current->state = PROCESS_READY;
    }

    //restore shared resource of next
    next->state = PROCESS_RUNNING;
    next->time_slice = 1;
    __current_process = next;

    /* Blende das virtuelle Datensegment mit vollen Schreibzugriffen an der Stelle 0xB0000000
     * für den nächsten Prozess ein */
    mmu_map_memory(
            (unsigned long)next->process_mem.data_segment.start,
            1,
            DATASEGMENT_VIRTUAL_ADDRESS,
            RWRW,
            MANAGER);
    mmu_map_memory(
            (unsigned long)next->process_mem.exec_segment.start,
            1,
            EXECSEGMENT_VIRTUAL_ADDRESS,
            RWRW,
            MANAGER);
    

    /* Eintrag im TLB für die Stelle 0xB0000000 als ungültig erklären */
    invalidate_data_tlb_at((void*)DATASEGMENT_VIRTUAL_ADDRESS);

    /* Eintrag im TLB für die Stelle 0xA0000000 als ungültig erklären */
    invalidate_data_tlb_at((void*)EXECSEGMENT_VIRTUAL_ADDRESS);
    //kprintf("RR lädt Prozess %i\n",next->process_id);
    //kprintf("Ausführbarer code bei %x, Datensegment bei %x\n", next->process_mem.exec_segment.start,next->process_mem.data_segment.start);
    //kprintf("cpustatus: %x\n", next->cpu_state->pc);
    switch_cpu( current ? &current->cpu_state : NULL, &next->cpu_state);
}
        

bool 
process_init()
{
    __current_process = NULL;
    process_id_seed = 1;
    wq_init( &active_list );

    return true;
}


// Implementierung des ladens einer Ramdisk

bool load_ramdisk(int i,char* c){
 
	char* name = ramdisk[i].name;
	unsigned long length = ramdisk[i].size;
	unsigned char* data = ramdisk[i].data;
	
	kprintf("Lade %s (%d bytes)\n",name, length);	
	//lade Speicher in der mmu für Prozesse
	kprintf("Lade Speicher für Prozess...\n");
	process_t *process = alloc_process();
	if(!process) {
	  return false;
	}
	kprintf("zugewiesener Programmspeicher: %x\n",process->process_mem.exec_segment.start); 
	kprintf("zugewiesener Datenspeicher: %x\n",process->process_mem.data_segment.start); 
	//auslesen des Headers
	exe_header_t header;
	
	//Kopiere aus data den magic-type
	unsigned char* ptr = data;
	memcpy(&header.magic, ptr, sizeof(uint32_t));
	
	//pointer incrementieren
	ptr += sizeof(uint32_t);
	//kopiere aus data den entrypoint
	memcpy(&header.entrypoint, ptr, sizeof(void*));
	
	//ptr inkrementieren
	ptr += sizeof(void*);
	
	//kprintf("Initialisiere Prozess im Runden Robin...\n");
	//Schreibe ausgelesen Entrypoint in den Prozesskontrollenblocck
	//TODO umbauen, das die Prozesse kein Übergabeparameter bekommen

	if(!init_process( process, (void*)header.entrypoint, c)) {
		free_process( process );
		return false;
	}

	    

	
	//Lade Seite des Prozesses an 0xB0000000‎
	mmu_map_memory(
		(unsigned long)process->process_mem.data_segment.start,
		1,
		DATASEGMENT_VIRTUAL_ADDRESS,
		RWRW,
		MANAGER);
	//Lade Datenspeicher des Prozesses
	mmu_map_memory(
		(unsigned long)process->process_mem.exec_segment.start,
		1,
		EXECSEGMENT_VIRTUAL_ADDRESS,
		RWRW,
		MANAGER);
	/* Eintrag im TLB für die Stelle 0xB0000000 als ungültig erklären */
	invalidate_data_tlb_at((void*)DATASEGMENT_VIRTUAL_ADDRESS);

	/* Eintrag im TLB für die Stelle 0xA0000000 als ungültig erklären */
	//auslesen der einzelnen Sectionheader
	section_header_t* segment = kmalloc(sizeof(section_header_t));
	segment->section_type = ST_EXEC;


	while(segment->section_type != ST_END){
	  kprintf("Lese Section Type ... "); //TODO Auskommentieren
	  memcpy(&segment->section_type, ptr, sizeof(uint32_t));
	  kprintf("Section Type ist %d\n", segment->section_type);
	  if(segment->section_type == ST_END){
	  //keine weiteren Daten vorhanden Sectiontype muss ST_END sein
	  //While abbrechen
	    break;
	  }
	  //weitere Daten oder ausführbarer Code vorhanden
	  ptr += sizeof(uint32_t);
	
	  memcpy(&segment->address, ptr, sizeof(uint32_t));
	  kprintf("Zieladresse: %x\n", segment->address); //TODO Auskommentieren
	  ptr += sizeof(uint32_t);
	  
	  memcpy(&segment->size, ptr, sizeof(uint32_t));
	  kprintf("Größe %x\n", segment->size); //TODO Auskommentieren
	  ptr += sizeof(uint32_t);
	  kprintf("Lese Daten\n"); //TODO Auskommentieren
	  if(segment->section_type == ST_EXEC){
	    //Ausführbarer code wird an die virtuelle Adresse 0xA0000000 des Prozesses
	    memcpy((void*)segment->address, ptr, segment->size);
	    ptr += segment->size;
	    //exec_segment_ptr += segment->size;
	  }else if(segment->section_type == ST_DATA){
	  //Konstantensegemente werde an die virtuelle Adresse 0xA0000000 geschrieben
	    memcpy((void*)segment->address, ptr, segment->size);
	    ptr += segment->size;
	    //exec_segment_ptr += segment->size;
	  }else if(segment->section_type == ST_BSS){
	    //wahrscheinlich muss hier nichts gemacht werden
	  }
	}
	
	//Lade Prozess in die Drehscheibe von runden Robin
	if(!start_process( process )) {
	  free_process( process );
	  return false;
	}  
	kprintf("%s erfolgreich geladen, Entrypoint ist: %x\n",name,header.entrypoint);
    
    //Prozesse erfolgreich aus der Ramdisk geladen
    return true;
}

